package lt.marsrutai.mobile.android.dao.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import lt.marsrutai.mobile.android.dao.annotation.DataField;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DtoFiller {

    private static final Logger logger = LoggerFactory.getLogger("DtoFiller");

    private static final String DTO_CLASS_PREFIX;

    static {
        String packageName = DtoFiller.class.getPackage().getName();
        DTO_CLASS_PREFIX = packageName.substring(0, packageName.lastIndexOf('.')) + ".dto.";
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> fillList(String functionName, Class<T> dtoClass, JSONArray jsonArray) throws JSONException {

        List<T> list = new ArrayList<T>();

        for (int i = 0; i < jsonArray.length(); i++) {
            if (dtoClass.equals(Integer.class) || dtoClass.equals(String.class)) {
                list.add((T) jsonArray.get(i));
            }
            else {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                list.add(fillObject(functionName, dtoClass, jsonObject));
            }
        }

        return list;
    }

    public <T> T fillObject(String functionName, Class<T> dtoClass, JSONObject jsonObject) throws JSONException {

        T object = createObject(dtoClass);

        Method[] methods = dtoClass.getDeclaredMethods();
        for (Method method : methods) {
            if ("set".equals(method.getName().substring(0, 3))) {
                fillField(functionName, method, object, jsonObject);
            }
        }

        return object;
    }

    @SuppressWarnings("unchecked")
    private <T> T createObject(Class<?> cls) {
        try {
            return (T) cls.newInstance();
        }
        catch (IllegalAccessException e) {
            throw new IllegalArgumentException(e);
        }
        catch (InstantiationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private void fillField(String functionName, Method setter, Object dto, JSONObject jsonObject) throws JSONException {
        String fieldName = null;
        String nodeName = setter.getName().substring(3); 
        
        if(setter.isAnnotationPresent(DataField.class)) {
            fieldName = setter.getAnnotation(DataField.class).value();
        }
        else {
            fieldName = nodeName;   
        }

        Class<?>[] parameterTypes = setter.getParameterTypes();
        if (parameterTypes.length != 1) {
            throw new IllegalArgumentException("Setter must have only one parameter." + setter);
        }

        String jsonFieldName = "";
        try {
            jsonFieldName = getValidName(fieldName, jsonObject);
        }
        catch (IllegalArgumentException ex) {
            logger.debug("No field {} found in remote data.", fieldName);
            return;
        }

        try {
            if (parameterTypes[0] == String.class) {
                String value = jsonObject.isNull(jsonFieldName) ? null : jsonObject.getString(jsonFieldName);
                setter.invoke(dto, value);
            }
            else if (parameterTypes[0] == Integer.class) {
                Integer value = jsonObject.isNull(jsonFieldName) ? null : jsonObject.getInt(jsonFieldName);
                setter.invoke(dto, value);
            }
            else if (parameterTypes[0] == Float.class) {
                Double value = jsonObject.isNull(jsonFieldName) ? null : jsonObject.getDouble(jsonFieldName);
                setter.invoke(dto, null == value ? null : value.floatValue());
            }
            else if (parameterTypes[0] == Boolean.class) {
                Boolean value = jsonObject.isNull(jsonFieldName) ? null : jsonObject.getBoolean(jsonFieldName);
                setter.invoke(dto, null == value ? null : value.booleanValue());
            }
            else if (parameterTypes[0] == List.class) {
                if (!jsonObject.isNull(jsonFieldName)) {
                    JSONArray jsonArray = jsonObject.getJSONArray(jsonFieldName);
                    if (jsonArray.length() == 0) {
                        return;
                    }
    
                    List<?> value = null;
                    Class<?> arrayElementClass = jsonArray.get(0).getClass();
                    if (arrayElementClass == JSONObject.class) {
                        value = fillList(functionName, getDtoClassForList(functionName, nodeName), jsonArray);
                    }
                    else {
                        value = fillList(functionName, arrayElementClass, jsonArray);
                    }
    
                    setter.invoke(dto, value);
                }
            }
            else {
                if (!jsonObject.isNull(jsonFieldName)) {
                    JSONObject jsonObj = jsonObject.getJSONObject(jsonFieldName);
                    Object value = fillObject(functionName, getDtoClassForObject(functionName, nodeName), jsonObj);
                    setter.invoke(dto, value);
                }
            }
        }
        catch (JSONException e) {
            throw e;
        }
        catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    private Class<?> getDtoClassForObject(String functionName, String nodeName) {
        return getDtoClass(functionName, nodeName, false);
    }
    
    private Class<?> getDtoClassForList(String functionName, String nodeName) {
        return getDtoClass(functionName, nodeName, true);
    }

    private Class<?> getDtoClass(String functionName, String nodeName, boolean isPlural) {
        try {
            // First check node - class configuration
            Class<?> clazz = NodeClassConfiguration.get(functionName, nodeName);

            // If function + node has no configuration, try resolving
            if (clazz == null) {
                String className = DTO_CLASS_PREFIX + nodeName.substring(0, nodeName.length() - (isPlural ? 1 : 0)) + "Dto";
                clazz = Class.forName(className);
            }
            
            return clazz;
        }
        catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private String getValidName(String fieldName, JSONObject jsonObject) {
        @SuppressWarnings("unchecked")
        Iterator<String> iter = jsonObject.keys();
        while (iter.hasNext()) {
            String key = iter.next();
            if (fieldName.toLowerCase().equals(trim(key.toLowerCase(), "__"))) {
                return key;
            }
        }

        throw new IllegalArgumentException("No valid field name for " + fieldName);
    }

    private String trim(String string, String chr) {
        while (string.startsWith(chr)) {
            string = string.substring(chr.length(), string.length());
        }
        while (string.endsWith(chr)) {
            string = string.substring(0, string.length() - chr.length());
        }
        return string;
    }
}
